home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1993…ch: Other People's Memory / ADC Developer CD (1993-03) (''Other People's Memory'')_iso / Dev.CD Mar 93.iso / Technical Documentation / Sample Code / DTS.Lib & Samples / DTS.Draw / TearOff.MDEF.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-10-22  |  16.3 KB  |  590 lines  |  [TEXT/MPS ]

  1. /*
  2. ** Apple Macintosh Developer Technical Support
  3. ** Simple Tear-Off Menu Definition
  4. **
  5. ** File:        TearOff.MDEF.c
  6. ** Written by:    C.K. Haun and Eric Soldan
  7. **
  8. ** Copyright © 1992 Apple Computer, Inc.
  9. ** All rights reserved.
  10. */
  11.  
  12.  
  13. /*
  14. What does it take to tear a menu off? Do you need to patch things?
  15. Do you need to write an assembly language interface to the Menu Manager? 
  16. Do I need weird stand-alone globals?
  17.  
  18. Nah, just a little MDEF.
  19.  
  20. And here it is.  This MDEF 'tears off', allowing you to know the user
  21. has torn it off, at which point you'll make a palette window for it.
  22. No patches, no hacking, just code.
  23.  
  24. This MDEF is also an Iconic MDEF, but the same technique will work
  25. with a Text menu, or anything else.
  26.  
  27. How The Tear Off Part Works:
  28. When the user is tracking around in a menu, you are periodically called to
  29. highlight and de-highlight the items the user is tracking over.
  30. This is the whole key to tearing off.
  31. When the user goes outside the bounding rectangle of the menu, the
  32. Menu Manager tells you to de-highlight the last item they were over, and
  33. does NOT tell you to highlight a new one.  
  34. This MDEF watches for that situation, when it sees that there are no items 
  35. highlighted (easier than you'd think) it checks to see if the user is
  36. dragging in the Desktop area (screen minus this menu and the menu bar) and 
  37. if they are, it drags a gray outline of the menu around until they
  38. 1) release the mouse.
  39. 2) go back in the menu or menu bar.
  40.  
  41. If they release the mouse in a valid area for a tear off (not in the menu bar or current menu)
  42. then the MDEF lets you, the application, know that the user tore off.
  43. It does this by passing back an item number of -1, that's the 
  44. flag to you that says "Hey, something tore it".
  45. The 'where' the user wants is a little sneakier.
  46. The Menu struture that this MDEF uses has a dummy item at the end.
  47. That dummy item is never drawn, it's just a placeholder.
  48. When the user tears-off, I stuff the rectangle that they tore off at
  49. in the last menu item, as bytes 1 through 8 in the string.
  50. So in your application, you can do a 
  51.     GetItem(theMenu,CountMItems(theMenu),tempString);
  52.     BlockMove((Ptr)tempString + 1), (Ptr)&myTornOffRect, sizeof(Rect));
  53. and create your palette at that rect.
  54.  
  55. Why not something like:
  56.     GetItem(theMenu,CountMItems(theMenu),tempString);
  57.     myTornOffRect = *(Rect *)(tempString + 1);
  58. Because this code will crash on 68000 machines (a move with an odd address, ya know).
  59. So do the ugly BlockMove and you'll be happy.
  60.  
  61. Pretty simple, huh?
  62.  
  63.  
  64. Pay the most attention to the code in the ChooseIt and DragTearOff functions.
  65.  
  66. How The Icon Part Works:
  67. Drawing icons instead of text in a menu is not hard, the only thing that's
  68. at all interesting in that part of the code is how I store the IDs of the
  69. menu icons. 
  70.  
  71. I do NOT store the actual numeric values of the icon ids in the menu structure.
  72. You could, but then you have to actually *think* when you are building your
  73. menu in ResEdit or whereever, and I like to avoid extra thought when I can.
  74. So, I store the icon resource IDs in their string representation, so I do a
  75. StringToNum(...) to get the real number to pass to GetIcon.
  76. This has been extended to allow alternate cursors per menu item.  By marking the menu item,
  77. you can determine which icon to actually draw.  DTS.Draw uses this facility, so there is a
  78. complete sample demonstrating this.
  79.  
  80. The special dummy menu item on the end does more than just return the rect in bytes 1-8.
  81. It also allows you to use ResEdit to determine other attributes of the menu without
  82. having to modify and recompile the MDEF.  You can have 2 through 8 extra numbers after
  83. the length byte and 8 reserved bytes for the rect.  They are:
  84.  
  85. 1) Item height
  86. 2) Item width
  87. 3) Icon height
  88. 4) Icon width
  89. 5) Icon height offset
  90. 6) Icon width offset
  91. 7) Icon border height
  92. 8) Icon border width
  93.  
  94. All fields that are set to a value in ResEdit are assumed to be 0, except for the
  95. Icon height and Icon width fields.  These are assumed to be the same as the Item height
  96. and Item width unless otherwise stated.
  97. (You must have at least the Item height and Item width).
  98.  
  99.  
  100. • Things that AREN'T in here! •
  101. This MDEF does NOT scroll, support heirarchical menus, or PopUps.
  102. The thought of a Hierarchical tear-off makes my brain throb.
  103.  
  104. If you need more information about HMenus, scrolling, and Popups, please
  105. see the excellent MDEF sample called Concordia on the Developer CD.
  106.  
  107. Any bugs or comments....
  108. C.K. Haun or Eric Soldan
  109. Apple DTS (ALink : DEVSUPPORT)
  110.  
  111.  
  112. */
  113.  
  114. #include <Types.h>
  115. #include <Quickdraw.h>
  116. #include <Controls.h>
  117. #include <Events.h>
  118. #include <Memory.h>
  119. #include <ToolUtils.h>
  120. #include <Menus.h>
  121. #include <Packages.h>
  122. #include <Resources.h>
  123. #include <Script.h>
  124. #include <GestaltEqu.h>
  125.  
  126.  
  127. #define kSlop            6
  128. #define kWDEFTitleSize    9
  129. #define kExtremeNeg        -32767 + 1
  130. #define kExtremePos         32767 - 1 
  131.  
  132. #define kItemRect 0
  133. #define kIconRect 1
  134. #define kMaxNumParms 8
  135.  
  136. #define kItemHeight       0
  137. #define kItemWidth        1
  138. #define kIconHeight       2
  139. #define kIconWidth        3
  140. #define kIconHeightOffset 4
  141. #define kIconWidthOffset  5
  142. #define kIconBorderHeight 6
  143. #define kIconBorderWidth  7
  144.  
  145. #define kQDOriginal 0
  146.  
  147. pascal void main(short message, MenuHandle menu, Rect *menuRect, Point hit, short *item);
  148.  
  149. void        DrawItem         (MenuHandle menu, Rect menuRect, short item);
  150. void        ChooseIt         (MenuHandle menu, Rect menuRect, Point hit, short *item);
  151. Rect        CalcItemRect     (MenuHandle menu, Rect menuRect, short item, Boolean rectType);
  152. RgnHandle    CalcItemSelectRgn(MenuHandle menu, Rect menuRect, short item);
  153. short        GetNumItems      (MenuHandle menu);
  154. short        GetMenuData      (MenuHandle menu, short data[]);
  155. Boolean        DragTearOff      (Rect menuRect, Point thePoint, Rect *passedRect);
  156. Rect        FrameGrayRect    (Rect rct);
  157.  
  158. Rect        GetMainScreenRect(void);
  159. long        GetGestaltResult(OSType gestaltSelector);
  160.  
  161.  
  162.  
  163. /*****************************************************************************/
  164. /*****************************************************************************/
  165.  
  166.  
  167.  
  168. pascal void main(short message, MenuHandle menu, Rect *menuRect, Point hit, short *item)
  169. {
  170.     short    qq, numItems, data[kMaxNumParms];
  171.  
  172.     switch (message) {
  173.         case mDrawMsg:
  174.             numItems = GetNumItems(menu);
  175.             for (qq = 1; qq <= numItems; qq++) DrawItem(menu, *menuRect, qq);
  176.             break;
  177.  
  178.         case mChooseMsg:
  179.             ChooseIt(menu, *menuRect, hit, item);
  180.             break;
  181.  
  182.         case mSizeMsg:
  183.             numItems = GetMenuData(menu, data);
  184.             (*menu)->menuHeight = data[kItemHeight] * numItems - 1;
  185.             (*menu)->menuWidth  = data[kItemWidth];
  186.             break;
  187.  
  188.         case mPopUpMsg:            /* I am not supporting Popups, so I'm ignoring these */
  189.         case mDrawItemMsg:
  190.         case mCalcItemMsg:
  191.             break;
  192.     }
  193. }
  194.  
  195.  
  196.  
  197. /*****************************************************************************/
  198.  
  199.  
  200.  
  201. /* ChooseIt is the main workhorse routine. */
  202. /* The Menu Manager calls this routine continually during  */
  203. /* tracking to highlight and dehighlight the items the user is tracking over */
  204.  
  205. void    ChooseIt(MenuHandle menu, Rect menuRect, Point hit, short *item)
  206. {
  207.     short        ii, numItems;
  208.     RgnHandle    rgn;
  209.     Str63        infoText;
  210.     Rect        itemRect, tearRect;
  211.     Boolean        justUnselected;
  212.  
  213.     numItems = GetNumItems(menu);
  214.  
  215.     if (*item == -1) {
  216.         GetItem(menu, numItems + 1, infoText);
  217.         BlockMove(infoText + 1, (Ptr)&tearRect, sizeof(Rect));
  218.         InsetRect(&tearRect, -1, -1);
  219.         FrameGrayRect(tearRect);
  220.         return;
  221.     }
  222.  
  223.     if ((*item < 0) || (*item > numItems)) return;
  224.         /* Make sure values are valid. */
  225.  
  226.     for (ii = numItems; ii; --ii) {
  227.         itemRect = CalcItemRect(menu, menuRect, ii, kItemRect);    /* Get rect for first/next item. */
  228.         if (PtInRect(hit, &itemRect)) break;                    /* Are we in an item?  If so, we
  229.                                                                 ** know what we want to know. */
  230.     }
  231.  
  232.     justUnselected = false;
  233.  
  234.     if (*item != ii) {                /* If things have changed... */
  235.  
  236.         if (*item) {                /* If we had an old selected item, deselect it. */
  237.             rgn = CalcItemSelectRgn(menu, menuRect, *item);
  238.             InvertRgn(rgn);
  239.             DisposeRgn(rgn);
  240.             justUnselected = true;
  241.         }
  242.  
  243.         if (*item = ii) {        /* If we have a new selected item, select it. */
  244.             rgn = CalcItemSelectRgn(menu, menuRect, ii);
  245.             InvertRgn(rgn);
  246.             DisposeRgn(rgn);
  247.         }
  248.     }
  249.  
  250.     if (justUnselected) {        /* If we just unselected an item... */
  251.  
  252.         if (!ii) {                /* If we haven't selected a new item... */
  253.  
  254.             if (DragTearOff(menuRect, hit, &tearRect)) {
  255.  
  256.                 /* if this returned true, then they let up on the mouse whilst they were dragging */
  257.                 /* the outline, otherwise they went back and we press on */
  258.                 /* So, the final rectangle is in passedRect. */
  259.                 /* And, as I mentioned above, we have a dummy item at the end of our menu. */
  260.                 /* That dummy item is where we will sneakily store the rectangle, like so... */
  261.  
  262.                 GetItem(menu, ++numItems, infoText);
  263.                 BlockMove((Ptr)&tearRect, infoText + 1, sizeof(Rect));
  264.                 SetItem(menu, numItems, infoText);
  265.  
  266.                 *item = -1;
  267.             }
  268.         }
  269.     }
  270. }
  271.  
  272.  
  273.  
  274. /*****************************************************************************/
  275.  
  276.  
  277.  
  278. /* DrawItem draws our item.
  279. /* In this MDEF, I grab the text of the menu item, change it  */
  280. /* to a number, and then get an icon with that resID */
  281. /* and plot that icon  */
  282.  
  283. void DrawItem(MenuHandle menu, Rect menuRect, short theItem)
  284. {
  285.     Rect        itemRect, iconRect;
  286.     Str32        itemText, str;
  287.     long        num;
  288.     short        mark, i, ii;
  289.     Handle        itemIcon;
  290.     RgnHandle    oldClip, newClip;
  291.  
  292.     GetItemMark(menu, theItem, &mark);
  293.     if (mark >= '0') mark -= '0';
  294.  
  295.     GetItem(menu, theItem, itemText);
  296.     itemText[++itemText[0]] = ',';            /* Give us a terminator character. */
  297.     for (ii = 0; mark > -1; --mark) {
  298.         if (ii >= itemText[0]) break;
  299.         for (i = 0; itemText[++ii] != ','; ++i);
  300.         BlockMove(itemText + ii - i, str + 1, str[0] = i);
  301.         StringToNum(str, &num);
  302.     }
  303.  
  304.     if (itemIcon = GetResource('ICN#', num)) {
  305.         iconRect = CalcItemRect(menu, menuRect, theItem, kIconRect);
  306.         SectRect(&iconRect, &menuRect, &iconRect);
  307.         oldClip = NewRgn();
  308.         newClip = NewRgn();
  309.         RectRgn(newClip, &iconRect);
  310.         GetClip(oldClip);
  311.         SetClip(newClip);
  312.         iconRect.right  = iconRect.left + 32;
  313.         iconRect.bottom = iconRect.top  + 32;
  314.         PlotIcon(&iconRect, itemIcon);
  315.         SetClip(oldClip);
  316.         DisposeRgn(oldClip);
  317.         DisposeRgn(newClip);
  318.         ReleaseResource(itemIcon);
  319.         itemRect = CalcItemRect(menu, menuRect, theItem, kItemRect);
  320.         itemRect.top = itemRect.bottom - 1;
  321.         FrameRect(&itemRect);
  322.     }
  323. }
  324.  
  325.  
  326.  
  327. /*****************************************************************************/
  328.  
  329.  
  330.  
  331. /* DragTearOff */
  332. /* DragTearOff does the actual tearing off */
  333. /* As long as the mouse stays outside the menu and the menu bar area, I will  */
  334. /* drag a rectangle around. */
  335. /* This ends if */
  336. /* 1) The user lets up on the mouse.  In that case, I return the ending rectangle  */
  337. /*    and a TRUE */
  338. /* 2) The user goes back into the menu or menu bar.  In that case, I return FALSE */
  339.  
  340. Boolean DragTearOff(Rect menuRect, Point thePoint, Rect *passedRect)
  341. {
  342.     Rect        dragRect, menubarRect;
  343.     Point        endPoint, pp;
  344.     long        ll;
  345.     Boolean        dragRectOn;
  346.  
  347.     if (PtInRect(thePoint, &menuRect)) return(false);
  348.         /* If currently in the menu, get out of here. */
  349.  
  350.     menubarRect = GetMainScreenRect();        /* Calc the menubar rect. */
  351.     menubarRect.bottom = menubarRect.top + GetMBarHeight();
  352.  
  353.     if (PtInRect(thePoint, &menubarRect)) return(false);
  354.         /* If currently in the menubar, get out of here. */
  355.  
  356.     dragRect = menuRect;        /* Drag this puppy around until the user lets go,
  357.                                 ** or until the user bumps into the badRgn. */
  358.  
  359.     InsetRect(&dragRect, kSlop, kSlop);
  360.     ll = PinRect(&dragRect, thePoint);
  361.     InsetRect(&dragRect, -kSlop, -kSlop);
  362.  
  363.     pp = *(Point *)≪
  364.     OffsetRect(&dragRect, thePoint.h - pp.h, thePoint.v - pp.v);
  365.         /* Get the rect to be over the mouse location by at least a little slop. */
  366.  
  367.     InsetRect(&dragRect, -1, -1);
  368.  
  369.     /* Keep tracking the mouse and moving the rect until they release the mouse button,
  370.     ** or until they bump into the menu or the menubar. */
  371.  
  372.     dragRectOn = false;
  373.  
  374.     while (StillDown()) {
  375.  
  376.         GetMouse(&endPoint);
  377.  
  378.         if (!EqualPt(endPoint, thePoint)) {                /* They moved... */
  379.  
  380.             if (dragRectOn) {
  381.                 FrameGrayRect(dragRect);                /* Erase the old rect */
  382.                 dragRectOn = false;
  383.             }
  384.  
  385.             if (PtInRect(thePoint, &menuRect)) break;
  386.             if (PtInRect(thePoint, &menubarRect)) break;
  387.  
  388.             OffsetRect(&dragRect, endPoint.h - thePoint.h, endPoint.v - thePoint.v);
  389.             thePoint = endPoint;
  390.  
  391.             InsetRect(&menuRect, -kSlop, -kSlop);
  392.             dragRectOn = !PtInRect(thePoint, &menuRect);
  393.             InsetRect(&menuRect, kSlop, kSlop);
  394.  
  395.             if (dragRectOn) FrameGrayRect(dragRect);    /* Frame the new position */
  396.             Delay(1, &ll);                                /* Reduce tearing. */
  397.         }
  398.     }
  399.  
  400.     if (dragRectOn) dragRect = FrameGrayRect(dragRect);        /* Erase the gray region. */
  401.     InsetRect(&dragRect, 1, 1);
  402.  
  403.     *passedRect = dragRect;            /* pass back the end rect position, whatever it is */
  404.     return(dragRectOn);                /* and go hither */
  405. }
  406.  
  407.  
  408.  
  409. /*****************************************************************************/
  410.  
  411.  
  412.  
  413. Rect    CalcItemRect(MenuHandle menu, Rect menuRect, short item, Boolean rectType)
  414. {
  415.     Rect    itemRect;
  416.     short    data[kMaxNumParms];
  417.  
  418.     GetMenuData(menu, data);
  419.  
  420.     itemRect.top = --item * data[kItemHeight];
  421.     switch (rectType) {
  422.         case kItemRect:
  423.             SetRect(&itemRect, 0, itemRect.top, data[kItemWidth], itemRect.top + data[kItemHeight]);
  424.             break;
  425.         case kIconRect:
  426.             SetRect(&itemRect, 0, itemRect.top, data[kIconWidth], itemRect.top + data[kIconHeight]);
  427.             OffsetRect(&itemRect, data[kIconWidthOffset], data[kIconHeightOffset]);
  428.             break;
  429.     }
  430.  
  431.     OffsetRect(&itemRect, menuRect.left, menuRect.top);
  432.     return(itemRect);
  433. }
  434.  
  435.  
  436.  
  437. /*****************************************************************************/
  438.  
  439.  
  440.  
  441. RgnHandle    CalcItemSelectRgn(MenuHandle menu, Rect menuRect, short item)
  442. {
  443.     Rect        rct;
  444.     RgnHandle    rgn, interiorRgn;
  445.     short        data[kMaxNumParms];
  446.  
  447.     rct = CalcItemRect(menu, menuRect, item, kItemRect);
  448.     --rct.bottom;
  449.     RectRgn(rgn = NewRgn(), &rct);
  450.  
  451.     GetMenuData(menu, data);
  452.     if (data[kIconBorderHeight] || data[kIconBorderWidth]) {
  453.         InsetRect(&rct, data[kIconBorderWidth], data[kIconBorderHeight]);
  454.         RectRgn(interiorRgn = NewRgn(), &rct);
  455.         DiffRgn(rgn, interiorRgn, rgn);
  456.         DisposeRgn(interiorRgn);
  457.     }
  458.  
  459.     return(rgn);
  460. }
  461.  
  462.  
  463.  
  464. /*****************************************************************************/
  465.  
  466.  
  467.  
  468. short    GetMenuData(MenuHandle menu, short data[])
  469. {
  470.     short    numItems, ii, parm, i;
  471.     Str63    infoText;
  472.     Str15    str;
  473.     long    num;
  474.  
  475.     for (i = 0; i < kMaxNumParms; ++i) data[i] = 0;
  476.  
  477.     GetItem(menu, (numItems = GetNumItems(menu)) + 1, infoText);
  478.     infoText[++infoText[0]] = ',';        /* Give us a terminator character. */
  479.  
  480.     ii = 8;
  481.     if (infoText[9] == ',') ++ii;        /* Skip possible first optional delimiter. */
  482.  
  483.     for (parm = 0; parm < kMaxNumParms; ++parm) {
  484.         if (ii >= infoText[0]) break;
  485.         for (i = 0; infoText[++ii] != ','; ++i);
  486.         BlockMove(infoText + ii - i, str + 1, str[0] = i);
  487.         StringToNum(str, &num);
  488.         data[parm] = num;
  489.         if (parm < 2) data[parm + 2] = num;
  490.     }
  491.  
  492.     return(numItems);
  493. }
  494.  
  495.  
  496.  
  497. /*****************************************************************************/
  498.  
  499.  
  500.  
  501. short    GetNumItems(MenuHandle menu)
  502. {
  503.     return(CountMItems(menu) - 1);
  504. }
  505.  
  506.  
  507.  
  508. /*****************************************************************************/
  509.  
  510.  
  511.  
  512. Rect    FrameGrayRect(Rect frameRect)
  513. {
  514.     PenState    thePen;
  515.     RgnHandle    oldClip;
  516.     Pattern        Gray;
  517.     Rect        rct;
  518.     short        mbh;
  519.  
  520.     GetPenState(&thePen);        /* I'm going to be changing pen information, so save the old */
  521.  
  522.     /* get the pattern I want from the System file, since I don't have */
  523.     /* access to Application Globals (A5) since I'm standalone */
  524.     /* I _could_ rely on the current port information, but I'm too */
  525.     /* paranoid for that */
  526.  
  527.     Gray[0] = 0;
  528.     GetIndPattern(Gray, sysPatListID, 4);
  529.  
  530.     GetClip(oldClip = NewRgn());
  531.     SetRect(&rct, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
  532.     ClipRect(&rct);        /* Allow drawing everywhere. */
  533.  
  534.     PenMode(patXor);            /* set up for gray dragging */
  535.     PenPat(&Gray);
  536.  
  537.     mbh = GetMBarHeight() + kWDEFTitleSize;
  538.     if (frameRect.top < mbh) OffsetRect(&frameRect, 0, (mbh - frameRect.top));
  539.     FrameRect(&frameRect);
  540.  
  541.     SetPenState(&thePen);        /* restore the pen */
  542.     SetClip(oldClip);            /* restore the original clip */
  543.     DisposeRgn(oldClip);
  544.  
  545.     return(frameRect);
  546. }
  547.  
  548.  
  549.  
  550. /*****************************************************************************/
  551.  
  552.  
  553.  
  554. Rect    GetMainScreenRect(void)
  555. {
  556.     short        qdVers;
  557.     GDHandle    mainDevice;
  558.     GrafPtr        mainPort;
  559.  
  560.     qdVers = (GetGestaltResult(gestaltQuickdrawVersion) >> 8) & 0xFF;
  561.  
  562.     if (qdVers > kQDOriginal) {
  563.         mainDevice = GetMainDevice();
  564.         return((*mainDevice)->gdRect);
  565.     }
  566.     else {
  567.         GetWMgrPort(&mainPort);
  568.         return(mainPort->portRect);
  569.     }
  570. }
  571.  
  572.  
  573.  
  574. /*****************************************************************************/
  575.  
  576.  
  577.  
  578. long    GetGestaltResult(OSType gestaltSelector)
  579. {
  580.     long    gestaltResult;
  581.  
  582.     if (Gestalt(gestaltSelector, &gestaltResult) == noErr)
  583.         return(gestaltResult);
  584.     else
  585.         return(0);
  586. }
  587.  
  588.  
  589.  
  590.